package com.flow.server.modules.instance.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.flow.server.common.exception.SysWorkflowException;
import com.flow.server.common.utils.PageUtil;
import com.flow.server.modules.base.service.impl.IBaseServiceImpl;
import com.flow.server.modules.cmd.BpmnBuilderUtil;
import com.flow.server.modules.cmd.ProcessInstanceCmd;
import com.flow.server.modules.instance.entity.ActProcessInstanceEntity;
import com.flow.server.modules.instance.entity.ActTaskHistoryEntity;
import com.flow.server.modules.instance.entity.ProcessInstanceEntity;
import com.flow.server.modules.instance.mapper.ActProcessInstanceMapper;
import com.flow.server.modules.instance.service.IInstanceRestService;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.activiti.bpmn.model.BpmnModel;
import org.activiti.engine.identity.User;
import org.activiti.engine.identity.UserQuery;
import org.activiti.engine.impl.persistence.entity.ExecutionEntity;
import org.activiti.engine.runtime.ProcessInstance;
import org.activiti.engine.runtime.ProcessInstanceBuilder;
import org.activiti.engine.runtime.ProcessInstanceQuery;
import org.activiti.engine.task.Task;
import org.activiti.image.ProcessDiagramGenerator;
import org.activiti.image.impl.DefaultProcessDiagramGenerator;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * 描述：流程实例 service 实现类
 * <p>
 * 作者：Ostrich Hu
 * 时间：2020/1/19 9:14 星期日
 */
@Slf4j
@Service
public class IInstanceRestServiceImpl extends IBaseServiceImpl implements IInstanceRestService {

    @Resource
    private ActProcessInstanceMapper actProcessInstanceMapper;

    @Override
    public boolean startProcessInstance(HttpServletRequest request) {
        try {
            ProcessInstanceCmd processInstanceCmd = BpmnBuilderUtil.buildProcessInstanceCmd(request);
            identityService.setAuthenticatedUserId(processInstanceCmd.getStartUserId());
            ProcessInstanceBuilder processInstanceBuilder = runtimeService.createProcessInstanceBuilder();
            processInstanceBuilder.businessKey(processInstanceCmd.getBusinessId());
            processInstanceBuilder.processDefinitionKey(processInstanceCmd.getProcessDefKey());
            processInstanceBuilder.name(processInstanceCmd.getInstanceName());
            processInstanceBuilder.variables(processInstanceCmd.getVariables());
            ProcessInstance instance = processInstanceBuilder.start();
            logger.debug("流程实例{}", instance);
            return Objects.nonNull(instance);
        } finally {
            identityService.setAuthenticatedUserId(null);
        }
    }

    @Override
    public Map<String, Object> getListPage(HttpServletRequest request) {
        ProcessInstanceCmd processInstanceCmd = BpmnBuilderUtil.buildProcessInstanceCmd(request);
        ProcessInstanceQuery processInstanceQuery = runtimeService.createProcessInstanceQuery();
        int pageNum = processInstanceCmd.getPageNum();
        int pageSize = processInstanceCmd.getPageSize();
        String instanceName = processInstanceCmd.getInstanceName();
        String category = processInstanceCmd.getCategory();
        if (StringUtils.isNoneBlank(instanceName)) {
            processInstanceQuery.processInstanceNameLike(instanceName);
        }
        if (StringUtils.isNoneBlank(category)) {
            processInstanceQuery.processDefinitionCategory(category);
        }
        long total = processInstanceQuery.count();
        List<ProcessInstance> listPage = processInstanceQuery.orderByProcessInstanceId().desc()
                .listPage(pageNum - 1, pageSize);

        UserQuery userQuery = identityService.createUserQuery();
        //转换数据类型
        CopyOnWriteArrayList<ProcessInstanceEntity> list = new CopyOnWriteArrayList<>();
        listPage.parallelStream().forEachOrdered(instance -> {
            ProcessInstanceEntity instanceEntity = new ProcessInstanceEntity(instance);
            if (Optional.ofNullable(instance.getStartUserId()).isPresent()) {
                User user = userQuery.userId(instance.getStartUserId()).singleResult();
                instanceEntity.setStartUserName(user.getLastName());
            }
            //设置当前节点信息
            List<Task> taskList = taskService.createTaskQuery().processInstanceId(instance.getId()).list();
            taskList.parallelStream().forEachOrdered(task -> {
                instanceEntity.setCurrNodeName(task.getName());
                instanceEntity.setCurrNodeDefKey(task.getTaskDefinitionKey());
                instanceEntity.setCurrNodeId(task.getId());
                instanceEntity.setRecentlyDoTime(task.getCreateTime());
                instanceEntity.setClaimTime(task.getClaimTime());
                if (Optional.ofNullable(task.getAssignee()).isPresent()) {
                    User user = userQuery.userId(task.getAssignee()).singleResult();
                    instanceEntity.setClaimUserName(user.getFirstName());
                }
            });
            list.add(instanceEntity);
        });

        Map<String, Object> result = Maps.newHashMap();
        result.put("data", list);
        result.put("total", total);
        result.put("pageNum", pageNum);
        result.put("pageSize", pageSize);
        return result;
    }

    @Override
    public void getRunWorkflowImage(String instanceId, HttpServletResponse response) {
        ExecutionEntity pi = (ExecutionEntity) runtimeService.createProcessInstanceQuery()
                .processInstanceId(instanceId).singleResult();
        BpmnModel model = repositoryService.getBpmnModel(pi.getProcessDefinitionId());
        ProcessDiagramGenerator generator = new DefaultProcessDiagramGenerator();

        try (OutputStream os = response.getOutputStream();
             InputStream resourceAsStream =
                     generator.generateDiagram(model, "png",
                             runtimeService.getActiveActivityIds(instanceId), Collections.emptyList(),
                             "宋体", "宋体", "宋体",
                             null, 1.0)
        ) {
            byte[] b = new byte[1024];
            int len = -1;
            while (((len = resourceAsStream.read(b, 0, 1024)) != -1)) {
                response.getOutputStream().write(b, 0, len);
            }
            //设置输出文件的信息
            response.setCharacterEncoding("UTF-8");
            response.setContentType("image/png;charset=utf-8");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
        } catch (IOException e) {
            throw new SysWorkflowException("获取流程实例图片异常", e);
        }
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
    @Override
    public PageUtil<ActProcessInstanceEntity> getInstanceListPage(ProcessInstanceCmd param) throws SysWorkflowException {
        Page<ProcessInstanceCmd> page = new Page<>(param.getPageNum(), param.getPageSize());
        QueryWrapper<ProcessInstanceCmd> query = Wrappers.query();
        if (StringUtils.isNotBlank(param.getStartUserId())) {
            query.eq("inst.START_USER_ID_", param.getStartUserId());
        }
        if (StringUtils.isNotBlank(param.getStartUserName())) {
            query.like("u.FIRST_", param.getStartUserName());
        }
        if (StringUtils.isNotBlank(param.getInstanceName())) {
            query.like("inst.NAME_", param.getInstanceName());
        }
        if (StringUtils.isNotBlank(param.getSuspensionState())) {
            query.eq("exe.SUSPENSION_STATE_", param.getSuspensionState());
        }
        if (StringUtils.isNotBlank(param.getCurrUserId())) {
            query.eq("rTask.ASSIGNEE_", param.getCurrUserId());
        }
        query.orderByDesc("inst.START_TIME_");
        IPage<ActProcessInstanceEntity> list = actProcessInstanceMapper.selectInstanceListPage(page, query);
        return new PageUtil<>(list.getRecords(), list.getTotal(), list.getSize(), list.getCurrent());
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
    @Override
    public List<ActTaskHistoryEntity> getTaskHistoryList(String instanceId) throws SysWorkflowException {
        return actProcessInstanceMapper.selectTaskHistoryList(instanceId);
    }

    @Transactional(readOnly = true, propagation = Propagation.REQUIRED, rollbackFor = Throwable.class)
    @Override
    public PageUtil<ActProcessInstanceEntity> getMyDoneInstancePage(ProcessInstanceCmd param) {
        Page<ProcessInstanceCmd> page = new Page<>(param.getPageNum(), param.getPageSize());
        QueryWrapper<ProcessInstanceCmd> query = Wrappers.query();
        if (StringUtils.isNotBlank(param.getStartUserId())) {
            query.eq("inst.START_USER_ID_", param.getStartUserId());
        }
        if (StringUtils.isNotBlank(param.getStartUserName())) {
            query.like("u.FIRST_", param.getStartUserName());
        }
        if (StringUtils.isNotBlank(param.getInstanceName())) {
            query.like("inst.NAME_", param.getInstanceName());
        }
        if (StringUtils.isNotBlank(param.getSuspensionState())) {
            query.eq("exe.SUSPENSION_STATE_", param.getSuspensionState());
        }
        if (StringUtils.isNotBlank(param.getCurrUserId())) {
            query.eq("hTask.ASSIGNEE_", param.getCurrUserId());
        }
        query.orderByDesc("inst.START_TIME_");
        IPage<ActProcessInstanceEntity> list = actProcessInstanceMapper.selectMyDoneInstancePage(page, query);
        return new PageUtil<>(list.getRecords(), list.getTotal(), list.getSize(), list.getCurrent());
    }
}
